package com.zzyy.study.mq.sender;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONUtil;
import com.zzyy.study.mq.po.MsgPO;
import com.zzyy.study.mq.retry.MqSendRetry;
import com.zzyy.study.mq.retry.MqSendRetryResult;
import com.zzyy.study.mq.service.IMsgService;
import com.zzyy.study.utils.CollUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.time.LocalDateTime;
import java.util.List;


@Slf4j
public class DefaultMsgSender implements IMsgSender
{
    //1 对t_msg表的增删改查业务类
    private IMsgService msgService;
    //2 对t_msg表status非1+是否重试+重试投递时间+重试次数等，进行重试投递的业务类
    private MqSendRetry mqSendRetry;
    //3 对标XXL-Job的定时任务类，补偿投递job，等价于XXL-Job
    //private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    //设计模式第2句话，组合优于继承 ---> spring容器里面的【构造注入】
    public DefaultMsgSender(IMsgService msgService,
                            MqSendRetry mqSendRetry,
                            ThreadPoolTaskExecutor threadPoolTaskExecutor)
    {
        this.msgService = msgService;
        this.mqSendRetry = mqSendRetry;
        //this.threadPoolTaskExecutor = threadPoolTaskExecutor;
    }


    @Override
    public void send(List<Object> msgList) {
        //将消息转换为json格式 ，{"id":"71b523dd305b43e0ba2ed7b0a028cd7a","name":"li4","age":12}
        List<String> msgBodyJsonList = CollUtils.convertList(msgList, JSONUtil::toJsonStr);


        log.info("msgBodyJsonList:{}",msgBodyJsonList);

        //当前是否有事务？
        boolean hasTransaction = hasTransaction();

        if (hasTransaction) {
            //有事务，则先入库,此时t_msg表status=0
            List<MsgPO> msgPOList = this.msgService.batchInsert(msgBodyJsonList);
            /**
             * TransactionSynchronizationManager.registerSynchronization方法
             * 作用概述
             * 允许开发者在Spring事务中注册一个事务同步器TransactionSynchronization对象。
             * 当事务提交、回滚或完成（提交或回滚后）时，这个注册的对象会被自动调用，从而执行与事务状态相关的操作。
             * 使用registerSynchronization时需要提供一个实现了TransactionSynchronization接口的类的实例。
             * 这个接口定义了一系列回调方法，
             * 如beforeCommit、afterCommit、afterRollback等，Spring会在事务生命周期的相应阶段自动调用这些方法
             */
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                @Override
                public void afterCompletion(int status) {
                    //为了提升性能：事务消息的投递消息这里异步去执行，即使失败了，会有补偿JOB进行重试

                    //threadPoolTaskExecutor.execute(() -> transactionAfter(msgPOList));

                    new Thread(() -> transactionAfter(msgPOList)).start();
                }
            });
        }
    }



    @Override
    public void sendRetry(MsgPO msgPO) {
        this.sendMsg(msgPO);
    }

    /**
     * 事务后执行的方法
     *
     * @param msgPOList
     */
    public void transactionAfter(List<MsgPO> msgPOList)
    {
        System.out.println("事务后执行的方法: "+ IdUtil.simpleUUID()+"\t"+msgPOList);
        //事务已经完成了(可能是回滚或者是提交了)看下本地消息记录是否存在？
        MsgPO msgPO = msgService.getById(msgPOList.get(0).getId());

        if (msgPO != null)
        {
            //如果存在 != null，说明事务是成功的，业务是执行成功的，则投递消息 & 并将消息状态置为成功
            for (MsgPO msg : msgPOList)
            {
                this.sendMsg(msg);
            }
            log.info("事务执行成功，消息投递完毕");
        }else{
            //进到这个eslse分支，说明事务执行失败了，消息未投递，由于插入消息和业务在一个事务中，
            // 事务执行失败，此时db中也是没有消息记录
            log.info("事务执行失败，本案例业务表(student)+消息表(t_msg)都没记录，不会向MQ投递消息");
        }
    }


    private void sendMsg(MsgPO msgPO)
    {
        Exception exception = null;

        try {
            //投递消息到MQ
            sendMsgToMQ(msgPO.getBodyJson());
        } catch (Exception e) {
            exception = e;
        }

        //没有异常，说明消息投递成功
        if (exception == null)
        {
            //将消息状态置为成功,t_msg消息表的status修改为1     SUCCESS(1, "已投递到MQ"),
            msgService.updateStatusSuccess(msgPO);
        } else {
            //有异常，则获取重试信息，将信息更新到db中
            MqSendRetryResult retryResult = this.mqSendRetry.getRetryResult(msgPO);
            //异常信息
            String failMsg = ExceptionUtil.stacktraceToString(exception);
            //是否需要重试？
            boolean retry = retryResult.isRetry();
            //下次重试时间
            LocalDateTime nextRetryTime = retryResult.getNextRetryTime();
            //将消息状态置为失败（且记录失败信息、是否需要重试，下次重试时间）
            //t_msg表status状态修改为2
            this.msgService.updateStatusFail(msgPO, failMsg, retry, nextRetryTime);

            //如果走到这里，还是失败，且默认重试次数已经到了，不需要重试了，建议告警
            if (!retry)
            {
                //todo 请执行告警代码，人工干预，发送钉钉或者短信......
            }
        }
    }

    @Resource
    private RabbitTemplate rabbitTemplate;
    /**
     * 开始投递消息到MQ
     * @param msgBodyJson
     */
    private void sendMsgToMQ(String msgBodyJson)
    {
        //todo 去实现将消息投递到mq的代码
        log.info("开始投递消息到MQ。。。。");
        rabbitTemplate.convertAndSend("myExchange01","simple01",msgBodyJson);
        log.info("消息体：{}", msgBodyJson);
        log.info("===========》消息已投递到MQ");
    }

    /**
     * 判断当前是否有事务？
     *
     * @return
     */
    private static boolean hasTransaction() {
        //开启了事务同步 && 并且当前确实有事务
        return TransactionSynchronizationManager.isSynchronizationActive()
                && TransactionSynchronizationManager.isActualTransactionActive();
    }


}
